#pragma once
#include "TcpServer.hpp"
#include "Socket.hpp"
#include "Util.hpp"
#include "Log.hpp"
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <functional>
#include <unordered_map>

using namespace SocketModule;
using namespace LogModule;

std::string gspace = " ";        // 空格
std::string glinespace = "\r\n"; // 换行符
std::string glinesep = ": ";     // 报头映射的分隔符

const std::string webroot = "../wwwroot";  // web根目录
const std::string homepage = "index.html"; // 首页
const std::string page_404 = "/404.html";  // 404的页面

// http的请求协议报头
class HttpRquest
{
public:
    HttpRquest()
        : _blankline(glinespace),
          _is_interact(false) // 默认是一个静态的网站
    {
    }

    // 把结构化成员转成大字符串的方法
    // 也就是序列化
    std::string Serialize()
    {
        return string();
    }

    // 对请求行进行反序列化的方法
    void ParseReqLine(std::string &reqline)
    {
        // GET / HTTP/1.1
        std::stringstream ss(reqline);
        // 上面例子中ss以空格为分隔符分别将数据流入_method，_uri，_version中
        ss >> _method >> _uri >> _version;
    }

    // 把大字符串转成下面各个成员的方法
    // 也就是反序列化
    // 请求需要实现
    bool Deserialize(std::string &reqstr)
    {
        // 1.提取请求行
        std::string reqline;
        bool res = Util::ReadOneLine(reqstr, &reqline, glinespace);
        LOG(LogLevel::DEBUG) << reqline;

        // 2.对请求行进行反序列化
        // 比如请求行：GET / HTTP/1.1
        // 就是把GET给_method，/给_uri，HTTP/1.1给_version
        ParseReqLine(reqline);

        // 如果uri为'/'，就代表要访问的是首页
        if (_uri == "/")
        {
            // 那就拼上weroot和首页地址
            _uri = webroot + _uri + homepage; //"../wwwroot/index.html"
        }
        else
        {
            // 这里就说明访问的不是首页，而是给了uri的路径
            _uri = webroot + _uri;
        }
        // 这里的uri可能是../wwwroot/login?username=zhangsan&password=123456

        // if (_method == "POST")
        // {
        //     _is_interact = true;
        //     while (true)
        //     {
        //         Util::ReadOneLine(reqstr, &reqline, glinespace);
        //         if (reqline != glinespace)
        //         {
        //             // 获得了request header->key value
        //             // Content-Length;
        //         }
        //         else
        //         {
        //             break;
        //         }
        //     }
        //     // Util::ReadOneLine(reqstr, &reqline, glinespace); // 正文数据
        // }

        LOG(LogLevel::DEBUG) << "Method: " << _method;
        LOG(LogLevel::DEBUG) << "URI: " << _uri;
        LOG(LogLevel::DEBUG) << "VERSION: " << _version;

        std::string temp = "?";
        auto pos = _uri.find(temp);
        if (pos == std::string::npos)
        {
            // 说明uri后面没有拿到表单信息
            //  到这说明_uri一定是../wwwroot/XXX.YYY这种形式的静态资源
            return true;
        }
        // 到这就说明uri后面有？，是有表单信息的，有要求执行的方法，需要交互
        _args = _uri.substr(pos + temp.size()); // username=zhangsan&password=123456
        // 那么真正的uri网址其实就是？前面的网址内容../wwwroot/login
        _uri = _uri.substr(0, pos);
        _is_interact = true;

        return true;
    }

    // 返回uri的方法
    std::string Uri()
    {
        return _uri;
    }

    // 返回判断是否需要交互的方法
    bool isInteract()
    {
        return _is_interact;
    }

    // 返回请求参数的方法
    std::string Args()
    {
        return _args;
    }

    ~HttpRquest()
    {
    }

    // private:
    //  http请求协议结构化成员
    //  1.请求行
    std::string _method;  // 请求方法
    std::string _uri;     // uri
    std::string _version; // http版本
    // 2.请求报头
    // http的报头属性 key value
    std::unordered_map<std::string, std::string> _headers;

    // 3.空行
    std::string _blankline;

    // 4.请求正文
    std::string _text;

    // 判断是否需要交互
    bool _is_interact;
    // 记录用户传过来请求的参数
    std::string _args;
};

// http的响应报头
class HttpResponse
{
public:
    HttpResponse()
        : _blankline(glinespace),
          _version("HTTP/1.0")
    {
    }

    // 把结构化成员转成大字符串的方法
    // 也就是序列化
    // 响应需要实现
    // 成熟的http的响应做序列化，不需要依赖任何第三方库！
    std::string Serialize()
    {
        // 1.状态行
        std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace;

        // 2. 响应行
        std::string resp_header;
        for (auto &header : _headers)
        {
            std::string line = header.first + glinesep + header.second + glinespace;
            resp_header += line;
        }

        // 3.空行在构造时就无参传了；4.正文是外面写的

        return status_line + resp_header + _blankline + _text;
    }

    // 设置一个目标资源的所处路径
    void SetTargetFile(const std::string &target)
    {
        _targetfile = target;
    }

    // 设置状态码的方法
    void SetCode(int code)
    {
        _code = code;
        switch (_code)
        {
        case 200:
            _desc = "OK";
            break;
        case 404:
            _desc = "Not Found";
            break;
        case 301:
            _desc = "Moved Permanently";
            break;
        case 302:
            _desc = "Set Other";
            break;
        default:
            break;
        }
    }

    // 设置报头的方法
    void SetHeader(const std::string &key, const std::string &value)
    {
        auto it = _headers.find(key);
        if (it != _headers.end())
        {
            // 说明这个kv已经存在
            return;
        }
        _headers.insert(std::make_pair(key, value));
    }

    // 从 _targetfile中提取后缀映射的方法
    std::string Uri2Suffix(std::string &targetfile)
    {
        // ../wwwroot/a/b/c.jpg
        // 先找到后缀，从右向左找到.，然后.和之后的字符串就是后缀
        int pos = targetfile.find(".");
        if (pos == std::string::npos)
        {
            // 说明没有找到后缀，在这默认是html的后缀，那么映射返回对应的是text/html
            return "text/html";
        }
        // 截取后缀
        std::string suffix = targetfile.substr(pos);
        // 这里我们做映射返回就返回这个demo需要的content-type类型映射就好
        // 正常服务器是要把整个表包含进来的！！
        if (suffix == ".html" || suffix == ".htm")
        {
            return "text/html";
        }
        else if (suffix == ".jpg" || suffix == ".jpeg")
        {
            return "image/jpeg";
        }

        return "";
    }

    // 填充应答（响应）的方法
    bool MakeResponse()
    {
        if (_targetfile == "../wwwroot/favicon.ico")
        {
            LOG(LogLevel::DEBUG) << "用户请求：" << _targetfile << "忽略它";
            return false;
        }

        if (_targetfile == "../wwwroot/redir_test") // 用户请求这个网址时
        {
            // 做一次临时重定向
            SetCode(302);
            SetHeader("Location", "https://www.qq.com"); // 这里以跳转到腾讯官网为例
            return true;
        }

        // 浏览器请求的资源不一定存在，这个路径是不一定存在于我们web根目录下的
        bool res = Util::ReadFileContent(_targetfile, &_text);
        int filesize = 0; // 记录目标文件大小的变量
        if (!res)         // 如果资源不存在
        {
            // _text = "";
            // LOG(LogLevel::WARNING) << "client want get: " << _targetfile << " but not found";
            // SetCode(404);                           // 设置状态码、状态码描述为404
            // _targetfile = webroot + page_404;       // 此时应该让用户访问的404页面
            // filesize = Util::FileSize(_targetfile); // 获取404文件大小
            // // 1.设置响应报头Content-Length
            // SetHeader("Content-Length", std::to_string(filesize));

            // // 2.Content-Type
            // // 根据请求资源的后缀得到Content-Type
            // std::string suffix = Uri2Suffix(_targetfile); // 从 _targetfile中提取后缀映射
            // SetHeader("Content-Type", suffix);

            // // 最后读取404文件内容到_text就好
            // Util::ReadFileContent(_targetfile, &_text);

            // 直接做临时重定向
            SetCode(302);
            // 这里用的是硬编码的域名
            SetHeader("Location", "http://192.168.85.128:8080//404.html");
            return true;
        }
        else
        {
            // 读取成功
            LOG(LogLevel::WARNING) << "client get: " << _targetfile << " success";
            SetCode(200);
            filesize = Util::FileSize(_targetfile); // 获取目标文件大小
            // 设置响应报头
            // 1.Content-Length
            SetHeader("Content-Length", std::to_string(filesize));

            // 2.Content-Type
            // 根据请求资源的后缀得到Content-Type
            std::string suffix = Uri2Suffix(_targetfile); // 从 _targetfile中提取后缀映射
            SetHeader("Content-Type", suffix);

            // 3.Set-Cookie
            SetHeader("Set-Cookie", "username=zhangsan; password=123456");
        }

        return true;
    }

    // 把大字符串转成下面各个成员的方法
    // 也就是反序列化
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }

    // 外部用于设置正文的方法
    void SetText(const std::string t)
    {
        _text = t;
    }

    ~HttpResponse() {}

    // private:
    //   http应答协议结构化成员
    //   1.状态行
    std::string _version; // http版本
    int _code;            // 状态码：用编号告诉客户端请求成功还是出错，出错原因，比如404
    std::string _desc;    // 状态码描述：对状态码返回出错信息的文字版描述，比如“Not Found”

    // 2.响应报头
    //  http的报头属性 key value
    std::unordered_map<std::string, std::string> _headers;

    // 3.空行
    std::string _blankline;

    // 4.响应正文
    std::string _text;

    // 其他属性
    // 目标资源所处路径
    std::string _targetfile;
};

using http_func_t = std::function<void(HttpRquest &req, HttpResponse &resp)>;
// 1.返回静态资源
// 2.提供动态交互的能力
class Http
{
public:
    Http(uint16_t port)
        : tsvrp(std::make_unique<TcpServer>(port))
    {
    }

    // 请求方法
    void HandlerHttpRquest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        // 收到请求
        std::string httpreqstr;
        // 假设：读到了完整的请求
        // 这个大概率是完整的，先不做处理了，可能有bug
        int n = sock->Recv(&httpreqstr); // 浏览器给我发过来的是一个大的http字符串
        if (n > 0)
        {
            // 读取成功，对这个httpreqstr进行处理
            // 对报文完整性进行审核
            HttpRquest req;
            // 做反序列化
            req.Deserialize(httpreqstr);
            HttpResponse resp;
            // 这里先做一下判断uri是否是静态资源还是说需要交互有要执行的方法
            if (req.isInteract())
            {
                // 在这说明是需要交互的
                //_uri：../wwwroot/login
                if (_route.find(req.Uri()) == _route.end())
                {
                    // 要请求的这个交互资源中要执行的方法不存在
                    // 返回404页面就好
                    resp.SetCode(302);
                }
                else
                {
                    // 这里说明该方法存在
                    // 那么我们这里使用value执行回调方法
                    _route[req.Uri()](req, resp);
                    // 回调完成之后序列化，再发送给客户端
                    std::string response_str = resp.Serialize();
                    sock->Send(response_str);
                }
            }
            else
            {
                // 这里说明不需要交互，为静态资源，直接设置就好
                resp.SetTargetFile(req.Uri()); // 设置一下目标资源的路径(在uri中)
                // 在这里我们就不担心用户访问一个服务器上不存在的资源了
                // 我们更不担心给用户返回任何网页资源（html、css、图片...）,这种资源为静态资源
                if (resp.MakeResponse())
                {
                    std::string response_str = resp.Serialize();
                    sock->Send(response_str);
                }
            }

            // // 这样通过uri拿到客户端要访问的资源路径
            // std::string filename = req.Uri();
            // // 然后把web根目录拼接上
            // //  ../wwwroot/a/b/c.html
            // //  这里给出响应
            // HttpResponse resp;
            // resp._version = "HTTP/1.1";
            // resp._code = 200;  // 状态码位200表示成功
            // resp._desc = "OK"; // 状态码描述表示成功

            // //../wwwroot/a/b/c.html
            // LOG(LogLevel::DEBUG) << "用户请求：" << filename;
            // // 浏览器请求的资源不一定存在，这个路径是不一定存在于我们web根目录下的
            // bool res = Util::ReadFileContent(filename, &(resp._text));
        }

// #ifndef DEBUG
// #define DEBUG
#ifdef DEBUG
        // 收到请求
        std::string httpreqstr;
        // 假设：读到了完整的请求
        sock->Recv(&httpreqstr); // 浏览器给我发过来的是一个大的http字符串
        // （这里的recv也是有问题的，tcp是面向字节流的）
        std::cout << httpreqstr;

        // 直接构建http应答，内存级别+固定
        HttpResponse resp;
        resp._version = "HTTP/1.1";
        resp._code = 200;  // 状态码位200表示成功
        resp._desc = "OK"; // 状态码描述表示成功

        std::string filename = webroot + homepage; // 要访问的文件:"../wwwroot/index.html"
        // 调用我们工具类中的静态从文件中读取内容的方法
        // 参数1为文件路径，参数2为输出型参数，用来接受从文件中读取的内容
        bool res = Util::ReadFileContent(filename, &(resp._text));
        (void)res;

        std::string response_str = resp.Serialize();
        sock->Send(response_str);

#endif
        // 对请求字符串进行反序列化
    }

    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                     { this->HandlerHttpRquest(sock, client); });
    }

    // 注册服务的方法
    void RegisterService(const std::string name, http_func_t h)
    {
        std::string key = webroot + name; //../wwwroot/login
        auto iter = _route.find(key);
        if (iter == _route.end())
        {
            // 新增服务
            _route.insert(std::make_pair(key, h));
        }
    }

private:
    std::unique_ptr<TcpServer> tsvrp;
    std::unordered_map<std::string, http_func_t> _route;
};